package com.zhanhong.opencvdemo.view;

import android.content.Context;
import android.hardware.Camera;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.zhanhong.opencvdemo.value.CameraSetting;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CameraView extends SurfaceView implements SurfaceHolder.Callback {


    private int mCamBackId = -1;
    private int mCamBackAngle = 0;
    private Camera mCamera;
    private int mScreenHeight = 0;
    private int mScreenWidth = 0;
    private boolean mIsPreviewing = false;

    public CameraView(Context context) {
        super(context);
        init();
    }

    public CameraView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CameraView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public CameraView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private void init() {
        getHolder().addCallback(this);
        getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
        mScreenHeight = displayMetrics.heightPixels;
        mScreenWidth = displayMetrics.widthPixels;
        mCamBackId = findBackCamera();
    }

    private int findBackCamera() {
        int cameraCount = Camera.getNumberOfCameras();
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        for (int i = 0; i < cameraCount; i++) {
            Camera.getCameraInfo(i, cameraInfo);
            if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                mCamBackAngle = cameraInfo.orientation;
                return i;
            }
        }
        return -1;
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        openCamera();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        stopPreview();
        initCamera();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        releaseCamera();
    }

    private void initCamera() {
        try {
            mCamera.setPreviewDisplay(getHolder());
            mCamera.setDisplayOrientation(getCamBackAngle());
            setCameraParameters();
            startPreview();
        } catch (Exception e) {
            e.printStackTrace();
            releaseCamera();
        }
    }

    private void setCameraParameters() {
        Camera.Parameters parameters = mCamera.getParameters();
        List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
        Collections.sort(supportedPreviewSizes, (o1, o2) -> o1.width - o2.width);
        List<Camera.Size> filterPreviewSizes = new ArrayList<>();
        for (Camera.Size supportedPreviewSize : supportedPreviewSizes) {
            if (supportedPreviewSize.width >= CameraSetting.MIN_PREVIEW_SIZE && supportedPreviewSize.height >= CameraSetting.MIN_PREVIEW_SIZE) {
                filterPreviewSizes.add(supportedPreviewSize);
            }
        }

        List<Camera.Size> supportedVideoSizes = parameters.getSupportedVideoSizes();
        Collections.sort(supportedVideoSizes, (o1, o2) -> o1.width - o2.width);
        List<Camera.Size> filterVideoSize = new ArrayList<>();
        for (Camera.Size supportedVideoSize : supportedVideoSizes) {
            if (supportedVideoSize.width >= CameraSetting.MIN_PREVIEW_SIZE && supportedVideoSize.height >= CameraSetting.MIN_PREVIEW_SIZE) {
                filterVideoSize.add(supportedVideoSize);
            }
        }

        filterPreviewSizes.retainAll(filterVideoSize);

        if (filterPreviewSizes.isEmpty()) {
            parameters.setPreviewSize(supportedPreviewSizes.get(supportedPreviewSizes.size() - 1).width, supportedPreviewSizes.get(supportedPreviewSizes.size() - 1).height);
        } else {
            Camera.Size acceptPreviewSize = findAcceptSize(filterPreviewSizes);
            parameters.setPreviewSize(acceptPreviewSize.width, acceptPreviewSize.height);
        }

        List<Camera.Size> supportedPictureSizes = parameters.getSupportedPictureSizes();
        Collections.sort(supportedPictureSizes, (o1, o2) -> o1.width - o2.width);
        List<Camera.Size> filterPictureSizes = new ArrayList<>();
        for (Camera.Size supportedPreviewSize : supportedPictureSizes) {
            if (supportedPreviewSize.width >= CameraSetting.MIN_PICTURE_SIZE && supportedPreviewSize.height >= CameraSetting.MIN_PICTURE_SIZE) {
                filterPictureSizes.add(supportedPreviewSize);
            }
        }

        if (filterPictureSizes.isEmpty()) {
            parameters.setPictureSize(supportedPictureSizes.get(supportedPictureSizes.size() - 1).width, supportedPictureSizes.get(supportedPictureSizes.size() - 1).height);
        } else {
            Camera.Size acceptPictureSize = findAcceptSize(filterPictureSizes);
            parameters.setPictureSize(acceptPictureSize.width, acceptPictureSize.height);
        }

        List<String> supportedFocusModes = parameters.getSupportedFocusModes();
        if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
        } else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
        } else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED);
        }

        mCamera.setParameters(parameters);
    }

    private Camera.Size findAcceptSize(List<Camera.Size> filterPreviewSizes) {
        float scaleDiff = 10F;
        Camera.Size acceptSize = filterPreviewSizes.get(0);
        for (Camera.Size filterPreviewSize : filterPreviewSizes) {
            int minValue = Math.min(filterPreviewSize.width, filterPreviewSize.height);
            int maxValue = Math.max(filterPreviewSize.width, filterPreviewSize.height);
            float curScaleDiff = Math.abs(1F * minValue / maxValue - 1F * mScreenWidth / mScreenHeight);
            if (scaleDiff - curScaleDiff >= 0.1F) {
                scaleDiff = curScaleDiff;
                acceptSize = filterPreviewSize;
            }
        }
        return acceptSize;
    }

    private void startPreview() {
        if (mCamera != null) {
            try {
                mCamera.startPreview();
                mIsPreviewing = true;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void openCamera() {
        if (mCamera != null) {
            mCamera.release();
            mCamera = null;
        }
        try {
            mCamera = Camera.open(mCamBackId);
        } catch (Exception e) {
            e.printStackTrace();
            mCamera.release();
            mCamera = null;
        }
    }

    private void stopPreview() {
        if (mIsPreviewing && mCamera != null) {
            try {
                mCamera.stopPreview();
                mIsPreviewing = false;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void releaseCamera() {
        stopPreview();
        if (mCamera != null) {
            mCamera.release();
            mCamera = null;
        }
    }

    public Camera getCamera() {
        return mCamera;
    }

    public int getCamBackAngle() {
        return mCamBackAngle;
    }
}
